Skip to content

Add 蹬王之王 (expiry-guardian): 额度到期雷达 + 无头守护进程 + Bot 看门狗(完整功能版;只读版见 #47) - #39

Closed
Hylouis233 wants to merge 1 commit into
MiniMax-AI:mainfrom
Hylouis233:feat/reset-card-v1.4-no-cron
Closed

Hylouis233 wants to merge 1 commit into
MiniMax-AI:mainfrom
Hylouis233:feat/reset-card-v1.4-no-cron

Conversation

@Hylouis233

@Hylouis233 Hylouis233 commented Oct 19, 2026 •

Copy link
Copy Markdown

这是什么

蹬王之王(Expiry Guardian) 的完整版。

  • Mini App 看板:积分与重置卡的到期雷达,额度到线自动暂停/恢复目标、到顶自动用卡
  • Rust 后台守护进程:通过 macOS launchd 常驻,与桌面端完全独立
  • Bot 看门狗:因临时性故障挂掉的 bot 复用原会话自动重新启用

本 PR 只新增 plugins/hylouis233/expiry-guardian/,另外在两个根 README 的
MiniApps 表里各加一行登记(上游校验要求每个插件都有登记行)。


⚠️ 这个 PR 与 #47 是二选一

管理员在 review 里提了三条社区插件的硬红线:

红线 本 PR(完整版) #47(只读版)
不能读 MiniMax Code 的登录凭据 ❌ 读 lease socket / .cap 取 token 调账号接口 ✅ 不读,数据源是本地账本
不能读写宿主内部数据库 ❌ UPDATE/DELETE/INSERT 目标与执行队列、读回合记录 ✅ 不碰
不能读别的插件的数据目录 ❌ 读 minimax-bot 的 state.json 拿 bot 与会话 id 去发消息 ✅ 不读,Bot 看门狗整体移除

⇒ 本 PR 是完整功能版(自动扣卡、自动暂停目标、自动叫醒 bot),
但它违反了上面三条红线。
⇒ #47 是同功能的只读版:同样的决策逻辑、同样的看板,
但数据源换成本地账本,且不碰宿主任何私有数据、不做任何写操作。

请 review 时先决定走哪条路:


两个设计要点

1. 彻底不依赖 cron

唤醒链路是「两段跳」:

Rust 守护进程(Host 之外)        蹬王之王自己的 MiniApp(Host 之内)
  判定谁挂了                        起一个 agent 回合
  写一条唤醒请求        ──────▶     该回合的 agent 用 mavis 工具的 session send
  记账                              把续跑消息送进目标 bot 的**原会话**

为什么不是 cron:实测 cron 目标会话会被运行时自动归档,跑完即触发
CRON_SESSION_TARGET_ARCHIVED。任何由专用宿主承载的值守都不可靠。
v1.4 起把 cron 相关代码删干净(goal_wake::set_cron_armed /
goal_sync::reconcile_resume_cron / config::resumeCronId / d_cron())。

2. 把「静默失效」变成会说话

「守护进程在跑、日志干净、实际一个 bot 都没被看护」比报错更坏。
这一版修掉了六个实例,每一个都在生产里实测复现过:

# 症状 根因
1 整个 Bot 看门狗静默失效(45 分钟零条日志) minimax-bot 状态文件里一个被截断的代理对转义,Python/Node 都能解析、只有 Rust 的 serde_json 严格拒绝 ⇒ 名单读不出来
2 11 次唤醒里 8 次是误判 拿会话上的 error_code 判「坏没坏」——那是陈旧值(运行时不清它)。那 8 个 bot 的最新回合其实是 completed。⇒ 改看 local_runtime_turn_ingress 里最新回合的状态
3 已投递过的请求每 30 分钟重投一次 acked 的 30 分钟窗口同时管「投递过了吗」与「多久能再救」。⇒ 拆成两个键:投递判据永久(唯一实例 id),频率限制单独用 bot 键 + 时间戳
4 「读不到 bot 名单」一个字都不打 守护进程看起来一切正常,实际是瞎的。⇒ 明确告警
5 守护进程 panic 崩溃循环 解析器按字节切 &str ⇒ 遇到中文 panic。⇒ 改纯字节解析 + catch_unwind 兜底
6 3 次失败封顶失效 ⇒ 无限唤醒 「按 acked 清理失败计数」在 acked 永久化后变成误伤("曾经被救过" ≠ "刚刚被救过")。⇒ 失败计数只在成功时归零

另外修掉两个「看板整个是坏的」:

  • doBuildSnapshot() 里一个拼写错误(platform → platformRes)⇒ 取数整个抛异常
    ⇒ 额度/决策/卡片全拿不到(v1.2 引入,活了 4 个版本)
  • MCP 工具漏了 await ⇒ JSON.stringify(Promise) = "{}" ⇒ watchdog_status 永远返回空

3. 唤醒链路自检

宿主重启后不会自动拉起任何 MiniApp ⇒ 那段时间「判定」照常、「投递」空转。
守护进程现在会明确告警,运行时恢复后也会说。
另有「投递空转」检测:运行时在跑但积压 15 轮不减少 ⇒ 告警。

从宿主外拉起 MiniApp 的每一条路都实测堵死(队列写入 / loopback 凭据 /
宿主 HTTP / deeplink / 官方插件),完整对照表见 docs/README.zh-CN.md。


其它

  • local MCP 端点:watchdog_status / watchdog_wake / watchdog_decide
    (刻意不暴露扣卡 —— 扣卡是真实写操作)
  • 客户端 401 自愈:连续 3 次 401 ⇒ 整页重载(上限 5 次)⇒ 面板不再卡在「实时连接中断」
  • 唤醒会话复用:唤醒不再每次冷启动(实测一轮唤醒 6 个 bot,新建会话数 = 0)
  • 瞬时错误不重发(BUSY / AGENT_OUTCOME_UNKNOWN)⇒ bot 不会收到重复的续跑消息
  • 一致性是测出来的:Rust / Python / MiniApp 三方决策逐字比对 19/19,
    且校验器带「注入漂移必须被抓到」的自检

校验

套件 结果
上游 npm run validate 本插件 OK(0 错 0 警)、repository: OK
上游 npm test 69/69
Rust 75/75
Python 决策单测 20/20
Mini App 118/118
三方一致性 19/19

文档

  • plugins/hylouis233/expiry-guardian/README.md —— 英文
  • plugins/hylouis233/expiry-guardian/docs/README.zh-CN.md —— 中文
  • plugins/hylouis233/expiry-guardian/docs/BOT_DIAGNOSIS.md —— 排查手册

Copilot AI balanced review requested due to automatic review settings October 19, 2026 17:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Hylouis233 Hylouis233 changed the title v1.4 蹬王之王:彻底移除 cron 依赖,唤醒改由 MiniApp 用 mavis 工具的 session send 投递 v1.4~v1.5 蹬王之王:彻底移除 cron 依赖,唤醒改由 MiniApp 用 mavis 工具的 session send 投递 + 唤醒链路全面加固 Oct 20, 2026

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢继续打磨!v1.5 复用唤醒会话、按次数给唤醒封顶、acked 改成带时间窗口而不是永久拉黑,这几处都是实打实的改进,测试也补得很全(reset-card-autouse 自带的测试在本地全部通过)。

不过上一轮的三个必改项在 0a433bf 上都还在,另外这版新增了一个校验错误:

需要修改

  1. 登录凭据(未修复):Node 入口(miniapp/node/server.mjs)和 Rust daemon(daemon/rust/src/api.rs)仍然在获取 MiniMax Code 的登录凭据并用来调用账号接口。社区插件不能读取 MiniMax Code 的登录凭据,这部分请先暂停。
  2. 内部数据库(未修复):daemon/rust/src/goal_sync.rs 仍通过 sqlite3 直接 UPDATE goal 状态,并把原因写成用户暂停。不能直接读写 MiniMax Code 的内部数据库,请删掉 goal 暂停/恢复这条路径(可以只在插件自己的数据目录里记账,并提示用户手动处理)。README 的 Data & access 里相应的说明也请一并更新。
  3. minimax-ai/minimax-bot 的改动请拆成单独的 PR(未修复):本 PR 仍包含对 plugins/minimax-ai/minimax-bot 的改动(版本到 0.7.4、重试策略、README 等)。这部分和"蹬王之王"不是同一件事,放在一起不方便分别 review 和回滚,请拆到单独的 PR,并在描述里写清楚改动理由和复现场景。另外:engine.mjs 第 83 行的 /\bbad request\b|\bnot found\b/ 匹配范围较宽,你自己新增的 turn-retry-forever.test.mjs 里「确定性失败不被当成暂时性错误」这一条在本地也是失败的(HTTP 400 Bad Request 被判成可重试),拆分时请一并修正,并给连续重试设个上限。
  4. 校验错误(新):v1.5 把 plugin.json 的 name 从 reset-card-autouse 改成了 expiry-guardian,npm run check 报 LAYOUT_NAME_MISMATCH(目录名和 name 必须一致)。请改回 reset-card-autouse,或者同时改目录名。

建议(可选)

  1. MCP 服务器名称:servers.mcp.json 里新增了一个叫 minimax-bot 的 MCP 服务器,与官方插件同名,容易让用户和模型混淆;且它和 expiry-guardian 指向同一个实现。建议只保留一个、用本插件自己的名字。
  2. MCP instructions 与工具不一致:initialize 的 instructions 里提到 watchdog_use_card,但工具列表里并没有这个工具(只有 status / wake / decide)。请删掉这句,避免模型去调用不存在的工具。watchdog_decide 只做判定不扣卡、扣卡仍须用户在面板上点按钮,这个设计很好,请保持。serverInfo.version 也还是 1.4.0。
  3. 唤醒机制:唤醒仍是起一个 Agent 回合、让模型给其他会话发消息。这依赖目前没有文档承诺的行为,以后可能失效;而且每次唤醒都会消耗 token、并往其他插件的会话里写消息。建议在 README 的 "Data & access" 里明确写出这三点,token 消耗统一成一个数字(目前文中有约 47k / 93k 两种说法)。
  4. 代码注释和 docs/BOT_DIAGNOSIS.md 里有不少针对宿主实现细节的推断,建议精简成"做了什么、对用户有什么影响",这些细节不保证稳定。

关于 #28:#39 已包含 #28 的全部内容,请关闭 #28,后续只在 #39 上继续。

本地结果(Node 20):npm run check 失败,reset-card-autouse 1 个错误(见第 4 条)、3 个警告;node --test:reset-card-autouse 全部通过;minimax-bot 的 turn-retry-forever.test.mjs 里 1 条失败(另有 7 个 minimax-bot 测试文件在 main 上同样失败,与本 PR 无关)。



// ── OAuth lease:与桌面端同一套登录态 ────────────────────────────────────────
const LEASE_SOCK = join(homedir(), '.minimax', 'run', 'mcode-auth-lease-v1.sock');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

必改:社区插件不能读取 MiniMax Code 的登录凭据,这部分请先暂停。


/// 向桌面端的 lease socket 取一个短期 access token。
/// 协议:4 字节大端长度前缀 + JSON。详见 packages/oauth-lease-protocol。
pub fn acquire_lease_token() -> Option<String> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

必改:同上,daemon 也不能读取 MiniMax Code 的登录凭据,这部分请先暂停。

BEGIN IMMEDIATE;\n\
CREATE TEMP TABLE _newly AS\n\
\x20 SELECT goal_id, session_id FROM local_runtime_thread_goals WHERE status='active';\n\
UPDATE local_runtime_thread_goals\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

必改:不能直接读写 MiniMax Code 的内部数据库。这里直接改 goal 状态并记为用户暂停,请移除这条路径。

@@ -0,0 +1,21 @@
{
"schemaVersion": 1,
"name": "expiry-guardian",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

必改:name 必须与目录名 reset-card-autouse 一致,当前 npm run check 报 LAYOUT_NAME_MISMATCH。

// 要求形如 `HTTP 400` / `status 404` / `code=422` / `400 Bad Request`。
/\b(?:http|status(?:\s+code)?|code)\s*[:=]?\s*(400|404|422)\b/i,
/\b(400|404|422)\s+(bad request|not found|unprocessable\w*)/i,
/\bbad request\b|\bnot found\b|\bunprocessable\w*\b/i,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

必改(拆分):请把 minimax-bot 的改动拆到单独 PR 并写明理由。另外这个模式会把 HTTP 400 Bad Request 判成可重试,本 PR 自带的 turn-retry-forever.test.mjs 对应用例在本地失败。

{
"schemaVersion": 1,
"mcpServers": {
"minimax-bot": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议:MCP 服务器名 minimax-bot 与官方插件同名,建议去掉,只保留本插件自己的名字。

serverInfo: { name: 'expiry-guardian', version: '1.4.0' },
instructions:
'蹬王之王:追踪额度与重置卡到期时间,并守护挂掉的 MiniMax Bot。' +
'用 watchdog_status 查状态、watchdog_wake 立即叫醒挂掉的 bot、watchdog_use_card 立即用掉该用的重置卡。',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议:instructions 提到的 watchdog_use_card 不存在,请删掉;serverInfo.version 也请更新。

* 而 45 个 bot 会话的 `purpose` 全是 `miniapp:minimax-bot`
* ⇒ 蹬王之王对这些会话直接 `agent.run` **永远被拒**(实测每 30 秒 6 条全拒)。
*
* ## 那怎么唤醒?—— 在一个 agent 回合里用 `mavis` 工具的 `session send`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议:这条唤醒路径依赖未公开的行为,请在 README "Data & access" 里说明,并写清每次唤醒的 token 消耗和会往其他会话写消息。

## Data & access

- **Files read:** `~/.config/mcode-reset-card/*.json` (its own config and ledgers),
`~/.minimax/v2/sqlite/runtime-state.sqlite` (read and, for goal status, written).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改完凭据和数据库两项后,请同步更新这里以及第 267 行的 Files read / Credentials 说明。

@Hylouis233 Hylouis233 changed the title v1.4~v1.5 蹬王之王:彻底移除 cron 依赖,唤醒改由 MiniApp 用 mavis 工具的 session send 投递 + 唤醒链路全面加固 v1.4~v1.6 蹬王之王:彻底移除 cron 依赖,唤醒改由 MiniApp 用 mavis 工具的 session send 投递 + 静默失效全部变成会说话 Oct 20, 2026

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢继续更新!这一轮的两个修复都很好:mcp.mjs 里 watchdog_status 补上了 await,MCP 工具不再永远返回 {};server.mjs:1027 的 platformRes → platform 拼写错误也修掉了,看板不会再因为它整个挂掉,而且都配了测试(tests/mcp.test.mjs、tests/server-lint.test.mjs,在 Node 20 上通过)。另外加了 .gitignore、把 __pycache__ 移出仓库,也很好。

不过这一版没有改动下面几项必须修改的问题(下面引用的行号在 78ec38e 上都已重新核对过),所以这次还是 Request changes。

必须修改

  1. 登录凭据(仍未修复)
    社区插件不能读取 MiniMax Code 的登录凭据,这部分请先暂停。目前 miniapp/node/server.mjs:39-40、daemon/rust/src/api.rs:197 和 daemon/reset_card_autouse.py:643-660 还在读 .cap 文件、通过 lease socket 取 access token,再拿它调用额度/重置卡接口。另外 reset_card_autouse.py:713-718 会把 token 写到 ~/.config/mcode-reset-card/token,后面 :735 还会从这里读回来。凭据不能落盘,这部分要一起去掉。

  2. 直接读写 MiniMax Code 内部数据库(仍未修复)
    插件不能直接打开 ~/.minimax/v2/sqlite/runtime-state.sqlite 来读写。

    • 改目标状态:goal_sync.rs:210、:279、:1232(UPDATE ...)。
    • 读回合记录:goal_sync.rs:428、bot_guard.rs:1073。
    • 直接删除:goal_sync.rs:932 purge_stale_session_sends → :967、goal_sync.rs:895、goal_wake.rs:453(DELETE FROM ...)。这些操作会直接删掉 MiniMax Code 自己的数据,删了就恢复不了,风险比 UPDATE 更大,请全部去掉。
  3. 读取其他插件的数据目录(仍未修复)
    bot_guard.rs:75-78 会读 minimax-bot 的 plugin-data/.../minimax-bot/state.json,拿到所有 bot 和会话 id,然后由 miniapp/node/bot_wakeup.mjs 向这些会话发消息把它们唤醒。插件只能用自己的数据,不能读其他插件的数据,更不能凭这些数据去操作其他插件的会话。请把这部分删掉。

  4. minimax-bot 的改动请拆成单独的 PR(仍未修复)
    PR 描述说这次不涉及 minimax-bot,但 diff 里还有 commit b446936 / 9a1cc5c 带进来的 7 个 plugins/minimax-ai/minimax-bot/* 文件(plugin.json 改成 0.7.4、engine.mjs、README、client、两个测试)。请把它们从这个 PR 里移出去,另开一个 PR 单独讨论(不是拒绝这些改动)。另外,tests/turn-retry-forever.test.mjs 在 Node 20 上还是有 1 项失败:HTTP 400 被当成可重试的错误了,这个可以在新 PR 里修。

  5. npm run check / npm run validate 不通过(仍未修复)
    LAYOUT_NAME_MISMATCH:目录名是 reset-card-autouse,.minimax-plugin/plugin.json:3 的 name 是 expiry-guardian,两者必须一致。请统一插件名和目录名(改目录名的话,PR 里的路径也要跟着改)。

建议(不阻塞)

  • server.mjs:1561 还在处理 /mcp/minimax-bot 路径,但端点已经从声明里删掉了,可以一起清理。
  • miniapp/node/*.test.mjs 会触发 ENTRY_STDOUT 警告,建议把测试统一放到 tests/ 下。这一版新加的 mcp.test.mjs、server-lint.test.mjs 在 miniapp/node/ 和 tests/ 下各有一份完全相同的副本,保留 tests/ 下的一份就够了。
  • 删掉上面这些逻辑之后,README / docs 里相应的说明也请同步更新。



// ── OAuth lease:与桌面端同一套登录态 ────────────────────────────────────────
const LEASE_SOCK = join(homedir(), '.minimax', 'run', 'mcode-auth-lease-v1.sock');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

社区插件不能读取 MiniMax Code 的登录凭据,这部分请先暂停。

/// 向桌面端的 lease socket 取一个短期 access token。
/// 协议:4 字节大端长度前缀 + JSON。详见 packages/oauth-lease-protocol。
pub fn acquire_lease_token() -> Option<String> {
let capability = std::fs::read_to_string(home().join(LEASE_CAP))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上:这里读凭据并取 access token,请删除。



# ── 命令 ──────────────────────────────────────────────────────────────────────
AUTH_CAP_FILE = os.path.expanduser("~/.minimax/run/mcode-auth-lease-v1.cap")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上:这里读凭据文件、通过 lease socket 取 token,请删除。

return token if isinstance(token, str) and token else None


def persist_token(token: str) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

token 不能落盘,请删除 persist_token,以及 :735 的读回逻辑。

BEGIN IMMEDIATE;\n\
CREATE TEMP TABLE _newly AS\n\
\x20 SELECT goal_id, session_id FROM local_runtime_thread_goals WHERE status='active';\n\
UPDATE local_runtime_thread_goals\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不能直接 UPDATE MiniMax Code 的内部数据库,请去掉。

continue;
}
let del = format!(
"DELETE FROM local_runtime_queue_items\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里会直接删除 MiniMax Code 自己的数据,而且无法恢复,请去掉(:895、goal_wake.rs:453 也一样)。

// 同一目标的**旧 epoch** 残留项也要清掉:它们永远不会被消费(epoch 对不上会被
// 准入判据 cancel),却会一直占着队列位置。
let stale = format!(
"DELETE FROM local_runtime_queue_items\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上,直接 DELETE,请去掉。

fn bot_state_path() -> Option<PathBuf> {
std::env::var("HOME").ok().map(|h| {
PathBuf::from(h)
.join(".minimax/v2/plugin-data/liveboards/minimax-bot/state.json")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里读的是其他插件的数据目录,请不要读,也不要据此唤醒其他插件的会话。

/** Failure reasons a retry cannot fix; anything else (or no reason from an older Host) may be retried. */
const NO_RETRY_REASONS = new Set(['auth', 'quota', 'context_too_long', 'blocked', 'invalid_request']);

/**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个文件和本插件无关,请拆到单独的 PR;另外 400 不应该重试。

@@ -0,0 +1,21 @@
{
"schemaVersion": 1,
"name": "expiry-guardian",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里和目录名不一致,会触发 LAYOUT_NAME_MISMATCH,导致 check 失败。

@Hylouis233
Hylouis233 force-pushed the feat/reset-card-v1.4-no-cron branch from 15d6578 to 02a6c11 Compare October 20, 2026 09:14
@Hylouis233 Hylouis233 changed the title v1.4~v1.6 蹬王之王:彻底移除 cron 依赖,唤醒改由 MiniApp 用 mavis 工具的 session send 投递 + 静默失效全部变成会说话 Add 蹬王之王 (reset-card-autouse): 额度到期雷达 + 无头守护进程 + Bot 看门狗(不依赖 cron,静默失效全部变成会说话) Oct 20, 2026
@Hylouis233
Hylouis233 force-pushed the feat/reset-card-v1.4-no-cron branch from 02a6c11 to 88e8a98 Compare October 20, 2026 09:24
@Hylouis233 Hylouis233 changed the title Add 蹬王之王 (reset-card-autouse): 额度到期雷达 + 无头守护进程 + Bot 看门狗(不依赖 cron,静默失效全部变成会说话) Add 蹬王之王 (expiry-guardian): 额度到期雷达 + 无头守护进程 + Bot 看门狗(不依赖 cron,静默失效全部变成会说话) Oct 20, 2026
@Hylouis233

Copy link
Copy Markdown
Author

关于上一轮 review 的回应

先谢过前两轮的意见。这一轮只动了不需要产品决策的部分:

已处理(可以在本地复现)

  • ✅ minimax-bot 的改动拆出去 —— 本 PR 现在不含任何 plugins/minimax-ai/** 改动,文件清单只剩 plugins/hylouis233/expiry-guardian/** 与两个根 README 的登记行。
  • ✅ LAYOUT_NAME_MISMATCH —— 目录名从 reset-card-autouse 改成 expiry-guardian,与 plugin.json / package.json 的 name 一致;根 README 的登记链接同步更新。
    (选改目录名而不是把 name 改回去:插件的对外身份本来就是「蹬王之王 / expiry-guardian」,MCP server 名、运行时归属判定都用这个名字。)
  • ✅ ENTRY_STDOUT 警告 —— miniapp/node/ 下那 5 份测试副本删掉了,只留 tests/ 下一份。
  • ✅ MCP server 与官方插件同名 —— 早前已改,只保留 expiry-guardian。
  • ✅ instructions 提到不存在的 watchdog_use_card —— 已删;serverInfo.version 也已是 1.6.0。
  • ✅ server.mjs 还在处理已删除的 /mcp/minimax-bot 路径 —— 已删。
  • ✅ Add 蹬王之王 / Expiry Sentinel: spend whichever credit expires first #28 已关闭(内容已被本 PR 取代,后续只在这里继续)。

校验结果:npm run validate 对本插件 OK(0 错 0 警)、repository: OK、npm test 69/69 通过。

还没动的三项,需要先定方向(这三条会决定插件还剩多少功能,所以想先听意见再动):

  1. 读取登录凭据(server.mjs 的 lease socket / .cap、api.rs 的 acquire_lease_token、Python 版的 token 落盘)
  2. 直接读写 MiniMax Code 内部 sqlite(goal_sync.rs 的 UPDATE local_runtime_thread_goals、goal_sync.rs/goal_wake.rs 的 DELETE FROM local_runtime_queue_items、以及读回合记录)
  3. 读取其他插件的数据目录(bot_guard.rs 读 minimax-bot 的 plugin-data/.../state.json 拿 bot 与会话 id,再往那些会话发消息)

我的理解是:第 1 条去掉后,额度/重置卡这条主线就没有数据源了(看板的到期雷达依赖账号接口);第 3 条去掉后,整个 Bot 看门狗就没有 bot 名单了(名字与会话 id 都来自 minimax-bot 的数据目录)。所以这三条不是「删几段代码」,而是要重新界定这个插件还做什么。

想请教一下:

  • 凭据这条,是有官方的替代接口(比如宿主暴露的配额 API / 授权能力)可以用,还是说社区插件这条路本身就先不通?那我按「先摘掉凭据读取、看板改成只显示本地可得的账本」来做?
  • 内部库这条,goal 的暂停/恢复是否直接整条拿掉(只在插件自己的数据目录记账,并提示用户手动处理)?
  • 跨插件读数据这条,Bot 看门狗是否本期先不做(等宿主提供官方的 bot 状态接口再说)?

如果按这个方向,剩下能交付的就是:到期雷达(只读本地账本)、触发线配置、决策逻辑与三方可执行性、以及那套「静默失效全部变成会说话」的加固。这三块我可以马上拆成独立 PR 发出来评审,不用等这三项定完。

(本 PR 先保持在「只含插件 + 根 README 登记」的状态,等方向定了我再改。)

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢这一轮的更新,也谢谢你在评论里把剩下的问题一条条列出来!这一版修好的部分都确认过了:

  • 目录改成了 plugins/hylouis233/expiry-guardian,和 plugin.json 的 name 一致;两个根 README 也补上了本插件那一行。npm run check / npm run validate 现在都通过了。
  • miniapp/node/ 下重复的测试删掉了,只保留 tests/ 下的一份(node --test tests/*.test.mjs 在 Node 20 上 6/6 通过)。
  • MCP 只保留了 expiry-guardian 一个端点,server.mjs 里 /mcp/minimax-bot 的路由也删掉了,不会再和官方插件重名。
  • watchdog_use_card 去掉了,mcp.mjs 的说明改成了“只判定不扣卡”的 watchdog_decide,测试里也确认了没有扣卡工具。

下面回答你问的三个问题(行号都在 88e8a98 上核对过)。这几项还在代码里,所以这次还是 Request changes。

必须修改

  1. 登录凭据
    社区插件不能读取 MiniMax Code 的登录凭据,这部分请先暂停。涉及的代码:miniapp/node/server.mjs:39-40、daemon/rust/src/api.rs:197、daemon/reset_card_autouse.py:643-660;另外 reset_card_autouse.py:713-718 把 token 写到 ~/.config/mcode-reset-card/token,:735 又从这里读回来,这部分也要一起去掉。

  2. 直接读写 MiniMax Code 内部数据库:请全部去掉
    插件不能直接打开 ~/.minimax/v2/sqlite/runtime-state.sqlite,读、改、删、插入都不行。

    • 改目标状态:goal_sync.rs:210、:279、:1232(UPDATE ...)。
    • 读回合记录:goal_sync.rs:428、bot_guard.rs:1158、:1161-1163(accepted_at_ms)。
    • 插入执行队列:goal_wake.rs:465(INSERT OR IGNORE INTO ...)。插件不能直接往 MiniMax Code 的执行队列里插入任务。
    • 直接删除:goal_sync.rs:895、:967(purge_stale_session_sends,:932)、goal_wake.rs:453(DELETE FROM ...)。

    也就是说,按现在的实现方式,Goal 暂停/恢复和 Bot 看门狗这两块这一轮请先整体移除。

  3. 读取其他插件的数据:请去掉
    bot_guard.rs:75-78 读的是 minimax-bot 的 state.json。插件只能用自己的数据,不能读其他插件的数据,也不能据此去唤醒其他插件的会话(miniapp/node/bot_wakeup.mjs)。

关于拆分

很欢迎你提的方案:把只读、能直接交付的部分(基于本地账本的到期雷达、触发配置、决策逻辑、静默失败的加固)单独开一个 PR。直接把这个 PR 裁到这个范围也可以,两种方式都行。只要不涉及上面三项,我们会尽快看。

另外,范围缩到只读的到期雷达之后,根目录 README.md / README.zh-CN.md 里本插件那一行(现在写的是自动暂停 Goal、自动恢复和唤醒)和插件自己的 README / docs 也要同步改,只写插件实际做的事情。

建议(不阻塞)

  • README.zh-CN.md:83 链到的是 plugins/hylouis233/expiry-guardian/README.zh-CN.md,但这个文件不存在(中文 README 在 docs/README.zh-CN.md)。请把链接改成 docs/README.zh-CN.md,或者在插件根目录放一份。
  • main.rs:387 的 catch_unwind 在 release 构建下不起作用:Cargo.toml:16 设了 panic = "abort",panic 时进程会直接退出,不会被这里接住。保留守护进程的话,二选一处理一下(README 里的说法也要对应改)。



// ── OAuth lease:与桌面端同一套登录态 ────────────────────────────────────────
const LEASE_SOCK = join(homedir(), '.minimax', 'run', 'mcode-auth-lease-v1.sock');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

社区插件不能读取 MiniMax Code 的登录凭据,这部分请先暂停。

/// 向桌面端的 lease socket 取一个短期 access token。
/// 协议:4 字节大端长度前缀 + JSON。详见 packages/oauth-lease-protocol。
pub fn acquire_lease_token() -> Option<String> {
let capability = std::fs::read_to_string(home().join(LEASE_CAP))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上:这里读凭据并取 access token,请删除。



# ── 命令 ──────────────────────────────────────────────────────────────────────
AUTH_CAP_FILE = os.path.expanduser("~/.minimax/run/mcode-auth-lease-v1.cap")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上:这里读凭据文件、通过 lease socket 取 token,请删除。

return token if isinstance(token, str) and token else None


def persist_token(token: str) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

token 不能落盘,请删除 persist_token,以及 :735 的读回逻辑。

BEGIN IMMEDIATE;\n\
CREATE TEMP TABLE _newly AS\n\
\x20 SELECT goal_id, session_id FROM local_runtime_thread_goals WHERE status='active';\n\
UPDATE local_runtime_thread_goals\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不能直接 UPDATE MiniMax Code 的内部数据库,请去掉(:279、:1232 也一样)。

continue;
}
let del = format!(
"DELETE FROM local_runtime_queue_items\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里会直接删除 MiniMax Code 自己的数据,而且无法恢复,请去掉(:895 也一样)。

// 同一目标的**旧 epoch** 残留项也要清掉:它们永远不会被消费(epoch 对不上会被
// 准入判据 cancel),却会一直占着队列位置。
let stale = format!(
"DELETE FROM local_runtime_queue_items\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上,直接 DELETE,请去掉。


// client_request_id 上有唯一索引,重复投递会被数据库挡住 ⇒ 天然幂等。
let sql = format!(
"INSERT OR IGNORE INTO local_runtime_queue_items\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

插件不能直接往 MiniMax Code 的执行队列里插入任务,请去掉。

IFNULL(s.error_code, 0) AS error_code, IFNULL(s.error_message, '') AS error_message,\n\
IFNULL((SELECT MAX(m.created_at_ms) FROM local_runtime_message_rows m\n\
WHERE m.session_id = s.session_id), 0) AS last_activity,\n\
(SELECT t.status FROM local_runtime_turn_ingress t\n\

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不能直接读内部数据库,请去掉(:1161 的 accepted_at_ms 子查询也一样)。

fn bot_state_path() -> Option<PathBuf> {
std::env::var("HOME").ok().map(|h| {
PathBuf::from(h)
.join(".minimax/v2/plugin-data/liveboards/minimax-bot/state.json")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里读的是其他插件的数据目录,请不要读,也不要据此唤醒其他插件的会话。

@Hylouis233
Hylouis233 force-pushed the feat/reset-card-v1.4-no-cron branch from 88e8a98 to 852152b Compare October 20, 2026 11:51
@Hylouis233 Hylouis233 changed the title Add 蹬王之王 (expiry-guardian): 额度到期雷达 + 无头守护进程 + Bot 看门狗(不依赖 cron,静默失效全部变成会说话) Add 蹬王之王 (expiry-guardian): 本地到期账本与用卡决策(v2.0 裁剪版:不碰宿主任何私有数据) Oct 20, 2026
Mini App 看板 + Rust 后台守护进程,通过 macOS launchd 常驻(与桌面端完全独立)。

## 两个设计要点

### 1. 彻底不依赖 cron

唤醒链路是「两段跳」:

```
Rust 守护进程(Host 之外)        蹬王之王自己的 MiniApp(Host 之内)
  判定谁挂了                        起一个 agent 回合
  写一条唤醒请求        ──────▶     该回合的 agent 用 mavis 工具的 session send
  记账                              把续跑消息送进目标 bot 的**原会话**
```

为什么不是 cron:实测 cron 目标会话会被运行时自动归档,跑完即触发
CRON_SESSION_TARGET_ARCHIVED。任何由专用宿主承载的值守都不可靠。

### 2. 把「静默失效」变成会说话

「守护进程在跑、日志干净、实际一个 bot 都没被看护」比报错更坏。这一版修掉六个实例,
每一个都在生产里实测复现过:

| # | 症状 | 根因 |
| --- | --- | --- |
| 1 | 整个 Bot 看门狗静默失效(45 分钟零条日志) | minimax-bot 状态文件里一个被截断的代理对转义,Python/Node 能解析、只有 Rust 的 serde_json 严格拒绝 ⇒ 名单读不出来 |
| 2 | 11 次唤醒里 8 次是误判 | 拿会话上的 error_code 判「坏没坏」——那是陈旧值(运行时不清它)。那 8 个 bot 的最新回合其实是 completed。改看 local_runtime_turn_ingress 里最新回合的状态 |
| 3 | 已投递过的请求每 30 分钟重投一次 | acked 的 30 分钟窗口同时管「投递过了吗」与「多久能再救」。拆成两个键:投递判据永久(唯一实例 id),频率限制单独用 bot 键 + 时间戳 |
| 4 | 「读不到 bot 名单」一个字都不打 | 守护进程看起来一切正常,实际是瞎的 ⇒ 明确告警 |
| 5 | 守护进程 panic 崩溃循环 | 解析器按字节切 &str ⇒ 遇到中文 panic。改纯字节解析 + catch_unwind 兜底 |
| 6 | 3 次失败封顶失效 ⇒ 无限唤醒 | 「按 acked 清理失败计数」在 acked 永久化后变成误伤。失败计数只在成功时归零 |

另外修掉两个「看板整个是坏的」:doBuildSnapshot() 里一个拼写错误(platform 写成
platformRes)让取数整个抛异常(v1.2 引入,活了 4 个版本);MCP 工具漏了 await ⇒
JSON.stringify(Promise) = "{}" ⇒ watchdog_status 永远返回空。

### 3. 唤醒链路自检

宿主重启后不会自动拉起任何 MiniApp ⇒ 那段时间「判定」照常、「投递」空转。
守护进程现在会明确告警,运行时恢复后也会说。另有「投递空转」检测。

## 其它

- **local MCP 端点**:watchdog_status / watchdog_wake / watchdog_decide(刻意不暴露扣卡)
- **客户端 401 自愈**:连续 3 次 401 ⇒ 整页重载(上限 5 次)
- **唤醒会话复用**:唤醒不再每次冷启动(实测一轮唤醒 6 个 bot,新建会话数 = 0)
- **瞬时错误不重发**(BUSY / AGENT_OUTCOME_UNKNOWN)⇒ bot 不会收到重复的续跑消息
- **一致性是测出来的**:Rust / Python / MiniApp 三方决策逐字比对 19/19,
  校验器带「注入漂移必须被抓到」的自检

## 校验

上游 npm run validate 对本插件 OK(0 错 0 警)、repository: OK、npm test 69/69。
本插件:Rust 75/75、Python 20/20、MiniApp 118/118、三方一致性 19/19。

## 文档

- plugins/hylouis233/expiry-guardian/README.md —— 英文
- plugins/hylouis233/expiry-guardian/docs/README.zh-CN.md —— 中文
- plugins/hylouis233/expiry-guardian/docs/BOT_DIAGNOSIS.md —— 排查手册
@Hylouis233
Hylouis233 force-pushed the feat/reset-card-v1.4-no-cron branch from 852152b to 9e27259 Compare October 20, 2026 12:26
@Hylouis233 Hylouis233 changed the title Add 蹬王之王 (expiry-guardian): 本地到期账本与用卡决策(v2.0 裁剪版:不碰宿主任何私有数据) Add 蹬王之王 (expiry-guardian): 额度到期雷达 + 无头守护进程 + Bot 看门狗(完整功能版;只读版见 #47) Oct 20, 2026
@Hylouis233

Copy link
Copy Markdown
Author

只读版已另开 #47

管理员 review 提的三条红线(不读宿主凭据 / 不读写宿主内部数据库 / 不读别的插件的数据),
只读版已经单独开在 #47:同样的看板与决策逻辑,数据源换成本地账本,
不碰宿主任何私有数据、不做任何写操作。

⇒ 两个 PR 是二选一:

决策逻辑两边一字未改(三方 19/19 逐字一致),差别只在「数据从哪来」
与「能不能自动执行」。

@Hylouis233

Copy link
Copy Markdown
Author

这个 PR 已被 #47 取代,先关掉,完整版保留在我自己的 fork 上。

原因:这一版仍然触碰了社区插件不能碰的三类宿主私有数据 ——

  • 读 MiniMax Code 的登录凭据(daemon/rust/src/api.rs 的 lease socket、daemon/reset_card_autouse.py 的 token 落盘)
  • 直接读写宿主内部数据库(daemon/rust/src/goal_sync.rs / goal_wake.rs / bot_guard.rs 里的 UPDATE / DELETE / INSERT)
  • 读别的插件的数据目录(bot_guard.rs 读 minimax-bot 的 state.json)

按 review 的要求("只要不涉及上面三项,我们会尽快看"),这三项必须拿掉才能被审。
⇒ 可合并的版本是 #47(只读版:本地到期账本 + 用卡决策,决策逻辑与本 PR 完全同构,
三方一致性仍 19/19 逐字一致)。

完整功能版(自动扣卡 / 目标暂停恢复 / Bot 看门狗)我会继续在自己的 fork 上维护和使用:
它依赖宿主的私有数据,不适合作为社区插件提交,但对自己是完全可用的。
需要时随时可以重开这个 PR。

@Hylouis233 Hylouis233 closed this Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants